package core.reports;

import static org.junit.Assert.*;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.SortedSet;
import java.util.TreeSet;

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

import persistence.DataPersistor;

import common.TimeSource;
import common.util.DateUtils;

import core.Container;
import core.ContainerFacade;
import core.Item;
import core.ItemFacade;
import core.ProductFacade;
import core.Product;
import core.ProductManager;

/*
 X 1: Test a report period that includes a transition from Standard Time to Daylight Savings Time
		a. The transition occurs in the middle of the report period
		b. The transition occurs on the first day of the report period
		c. The transition occurs on the last day of the report period 
 X 2: Test a report period that includes a transition from Daylight Savings Time to Standard Time
  		a. The transition occurs in the middle of the report period
		b. The transition occurs on the first day of the report period
		c. The transition occurs on the last day of the report period 
 X 5: Test running the report on a variety of days
 		a. Test running the report on the 31st of a month
 X 9: Test for reports run for a variety of inputs
 		a. Test for 0 months (should not run) 
 		e. Test for 101 months (should not run)
 * 10: Test a product that had a variety of types of usage during the period
 		a. Test for a product with only one addition and not used during the period
		b. Test for a product that was added multiple times, but was not used during the period
		c. Test for a product that was used once, but not added during the time period
		d. Test for a product that was used multiple times, but not added during the period
 X 11: Test that products are sorted correctly by description
 		a. Test with 2 products 
 X 13: Test that reports handles product data correctly
 		a. Short Description
		b. Long Description
		c. Regular Description
 * 15: Test used and current calculations
 		a. Test for Used Avg that is an integer
		b. Test for a Used Avg that has only one significant digit after decimal
		c. Test for Used Avg that has multiple significant digits after the decimal
		d. Test Used age max
 */

public class JustinProductStatsTest {

	private static ContainerFacade c_f;
	private static ItemFacade i_f;
	private static ProductFacade p_f;
	private static Random rand;
	private static String ALPHA;
	private static int PbarcodeCounter;
	private static Calendar now;
	private static Container SU;
	
	@BeforeClass
	public static void setUpBeforeClass() throws Exception {
		ALPHA = "abcdefghijklmnopqrstuvwxyz";
    	rand = new Random();
	}

	@AfterClass
	public static void tearDownAfterClass() throws Exception {
	}

	@Before
	public void setUp() throws Exception {
		DataPersistor.instance().init("-serial");
		ContainerFacade.clearAllData();
		ItemFacade.clearAllData();
		ProductFacade.clearAllData();
		c_f = ContainerFacade.instance();
		i_f = ItemFacade.instance();
		p_f = ProductFacade.instance();
		TimeSource.instance().reset();
		PbarcodeCounter = 0;
		now = Calendar.getInstance();
		SU = c_f.addStorageUnit("SU");
	}

	@After
	public void tearDown() throws Exception {
	}

	@Test
	public void test1abc() throws Exception {//Standard -> Daylight Savings (March 11, 2012)
		int expectedDays;
		Calendar before = Calendar.getInstance();
		Calendar middle = Calendar.getInstance();
		Calendar after = Calendar.getInstance();
		
		before.set(Calendar.MONTH, Calendar.MARCH);
		before.set(Calendar.DAY_OF_MONTH, 10);
		before.set(Calendar.YEAR, 2012);
		
		middle.set(Calendar.MONTH, Calendar.MARCH);
		middle.set(Calendar.DAY_OF_MONTH, 11);
		middle.set(Calendar.YEAR, 2012);
		
		after.set(Calendar.MONTH, Calendar.MARCH);
		after.set(Calendar.DAY_OF_MONTH, 12);
		after.set(Calendar.YEAR, 2012);
		
		//test 1a
		TimeSource.instance().setTime(before);
		Product p1 = new Product(getNextPBarcode(), "1_middleTest", "1", "count", 3, 3);
		p_f.addProduct(p1);
		p_f.addProductToContainer(p1, SU);
		
		//test 1b
		Product p2 = new Product(getNextPBarcode(), "2_firstDayTest", "1", "count", 3, 3);
		p_f.addProduct(p2);
		p_f.addProductToContainer(p2, SU);
		
		//test 1c
		Product p3 = new Product(getNextPBarcode(), "3_lastDayTest", "1", "count", 3, 3);
		p_f.addProduct(p3);
		p_f.addProductToContainer(p3, SU);
		
		//add a bunch of items
		Item i1 = i_f.addItem(p1, SU);
		TimeSource.instance().setTime(middle);
		Item i2 = i_f.addItem(p2, SU);
		TimeSource.instance().setTime(before);
		Item i3 = i_f.addItem(p3, SU);
		//remove a bunch of items
		TimeSource.instance().setTime(after);
		i_f.removeItem(i1);
		i_f.removeItem(i2);
		
		TimeSource.instance().setTime(middle);
		i_f.removeItem(i3);
		
		//run the report
		List<List<String>> data = runReport(3, after);
		//make sure the number of days between entry date and exit date is correct
		List<String> middleArray = data.get(1);
		assertEquals(middleArray.get(0), "1_middleTest");
		expectedDays = daysBetween(before, after);
		assertEquals(middleArray.get(8), expectedDays + " days /\n" + expectedDays + " days");
		
		List<String> onArray = data.get(2);
		assertEquals(onArray.get(0), "2_firstDayTest");
		expectedDays = daysBetween(middle, after);
		assertEquals(onArray.get(8), expectedDays + " days /\n" + expectedDays + " days");
		
		List<String> afterArray = data.get(3);
		assertEquals(afterArray.get(0), "3_lastDayTest");
		expectedDays = daysBetween(before, middle);
		assertEquals(afterArray.get(8), expectedDays + " days /\n" + expectedDays + " days");
	}
	
	@Test
	public void test2abc() throws Exception {//Daylight Savings -> Standard (November 4, 2012)
		int expectedDays;
		Calendar before = Calendar.getInstance();
		Calendar middle = Calendar.getInstance();
		Calendar after = Calendar.getInstance();
		
		before.set(Calendar.MONTH, Calendar.NOVEMBER);
		before.set(Calendar.DAY_OF_MONTH, 3);
		before.set(Calendar.YEAR, 2012);
		
		middle.set(Calendar.MONTH, Calendar.NOVEMBER);
		middle.set(Calendar.DAY_OF_MONTH, 4);
		middle.set(Calendar.YEAR, 2012);
		
		after.set(Calendar.MONTH, Calendar.NOVEMBER);
		after.set(Calendar.DAY_OF_MONTH, 5);
		after.set(Calendar.YEAR, 2012);
		
		//test 1a
		TimeSource.instance().setTime(before);
		Product p1 = new Product(getNextPBarcode(), "1_middleTest", "1", "count", 3, 3);
		p_f.addProduct(p1);
		p_f.addProductToContainer(p1, SU);
		
		//test 1b
		Product p2 = new Product(getNextPBarcode(), "2_firstDayTest", "1", "count", 3, 3);
		p_f.addProduct(p2);
		p_f.addProductToContainer(p2, SU);
		
		//test 1c
		Product p3 = new Product(getNextPBarcode(), "3_lastDayTest", "1", "count", 3, 3);
		p_f.addProduct(p3);
		p_f.addProductToContainer(p3, SU);
		
		//add a bunch of items
		Item i1 = i_f.addItem(p1, SU);
		TimeSource.instance().setTime(middle);
		Item i2 = i_f.addItem(p2, SU);
		TimeSource.instance().setTime(before);
		Item i3 = i_f.addItem(p3, SU);
		//remove a bunch of items
		TimeSource.instance().setTime(after);
		i_f.removeItem(i1);
		i_f.removeItem(i2);
		
		TimeSource.instance().setTime(middle);
		i_f.removeItem(i3);
		
		//run the report
		List<List<String>> data = runReport(3, after);
		//make sure the number of days between entry date and exit date is correct
		List<String> middleArray = data.get(1);
		assertEquals(middleArray.get(0), "1_middleTest");
		expectedDays = daysBetween(before, after);
		assertEquals(middleArray.get(8), expectedDays + " days /\n" + expectedDays + " days");
		
		List<String> onArray = data.get(2);
		assertEquals(onArray.get(0), "2_firstDayTest");
		expectedDays = daysBetween(middle, after);
		assertEquals(onArray.get(8), expectedDays + " days /\n" + expectedDays + " days");
		
		List<String> afterArray = data.get(3);
		assertEquals(afterArray.get(0), "3_lastDayTest");
		expectedDays = daysBetween(before, middle);
		assertEquals(afterArray.get(8), expectedDays + " days /\n" + expectedDays + " days");
	}
	
	@Test
	public void test5a11a() throws Exception {
		final int NUM_PRODUCTS = 2; //for test 11a
		final int DESC_LEN = 20;
		final int MONTHS = 50;
		Calendar cal = Calendar.getInstance();
		cal.set(Calendar.MONTH, Calendar.MARCH);
		cal.set(Calendar.DAY_OF_MONTH, 31); //for test 5a
		TimeSource.instance().setTime(cal);
		
		SortedSet<String> strings = new TreeSet<String>();
		while(strings.size() < NUM_PRODUCTS){
			strings.add(genRandString(DESC_LEN));
		}
		List<String> descList = new ArrayList<String>(strings);
		Collections.shuffle(descList);
		
		for(int i = 0; i < NUM_PRODUCTS; i++){
			Product p = new Product(getNextPBarcode(), descList.get(i), "1", "count", 3, 3);
			p_f.addProduct(p);
			p_f.addProductToContainer(p, SU);
		}
		
		Iterator<String> stringIter = strings.iterator();
		
		TimeSource.instance().reset();
		TestBuilder tb = new TestBuilder("Product Report: " + MONTHS + " Months");
		TimeSource.instance().setTime(now);
		ProductStatsVisitor visitor = new ProductStatsVisitor(tb, MONTHS);
		ProductManager.instance().accept(visitor);
		tb.print();
		List<List<String>> data = tb.getData();
		
		assertEquals(data.get(0).get(0), "Product Report: " + MONTHS + " Months");
		
		for(int row=1; row <= NUM_PRODUCTS && stringIter.hasNext(); row++){
			assertEquals(data.get(row).get(0), stringIter.next());
		}
	}
	
	@Test
	public void test9ae() throws Exception{
		final int ZEROMONTHS = 0; //test 9a
		final int TOOMANYMONTHS = 101; //test 9e
		
		try{
			runReport(ZEROMONTHS);
			fail("This report shouldn't have run; not enough months");
		}
		catch(Exception e){
			assertTrue("Passed 0 month reporting test", true);
		}
		
		try{
			runReport(TOOMANYMONTHS);
			fail("This report shouldn't have run; too many months");
		}
		catch(Exception e){
			assertTrue("Passed 101 month reporting test", true);
		}
	}
	
	@Test
	public void test13abc() throws Exception {
		final int MONTHS = 50;
		int lastYear = now.get(Calendar.YEAR)- 1;
		Calendar jan1st2012 = Calendar.getInstance();
		jan1st2012.set(Calendar.YEAR, lastYear);
		jan1st2012.set(Calendar.MONTH, Calendar.JANUARY);
		jan1st2012.set(Calendar.DAY_OF_MONTH, 1);
		Calendar mar1st2012 = Calendar.getInstance();
		mar1st2012.set(Calendar.YEAR, lastYear);
		mar1st2012.set(Calendar.MONTH, Calendar.MARCH);
		mar1st2012.set(Calendar.DAY_OF_MONTH, 1);
		
		TimeSource.instance().setTime(jan1st2012);
		Product shortDescription = new Product(getNextPBarcode(), "1_13a", "1", "count", 3, 3);
		String longDesc = "2_13b long description. This description is so long it hurts to even "
				+ "think about. I'm not really sure that it's really even healthy for a simple "
				+ "description to be this long. I guess I could start doing that lorem ipsum "
				+ "garbage, but I'm not sure that anyone would really appreciate that. I think "
				+ "this description is quite long enough, don't you? Oh no! It's starting over!"
				+ "This description is so long it hurts to even "
				+ "think about. I'm not really sure that it's really even healthy for a simple "
				+ "description to be this long. I guess I could start doing that lorem ipsum "
				+ "garbage, but I'm not sure that anyone would really appreciate that. I think "
				+ "this description is quite long enough, don't you? Oh no! It's starting over!"
				+ "This description is so long it hurts to even "
				+ "think about. I'm not really sure that it's really even healthy for a simple "
				+ "description to be this long. I guess I could start doing that lorem ipsum "
				+ "garbage, but I'm not sure that anyone would really appreciate that. I think "
				+ "this description is quite long enough, don't you? Oh no! It's starting over!"
				+ "This description is so long it hurts to even "
				+ "think about. I'm not really sure that it's really even healthy for a simple "
				+ "description to be this long. I guess I could start doing that lorem ipsum "
				+ "garbage, but I'm not sure that anyone would really appreciate that. I think "
				+ "this description is quite long enough, don't you? Oh no! It's starting over!"
				+ "This description is so long it hurts to even "
				+ "think about. I'm not really sure that it's really even healthy for a simple "
				+ "description to be this long. I guess I could start doing that lorem ipsum "
				+ "garbage, but I'm not sure that anyone would really appreciate that. I think "
				+ "this description is quite long enough, don't you? Oh no! It's starting over!"
				+ "This description is so long it hurts to even "
				+ "think about. I'm not really sure that it's really even healthy for a simple "
				+ "description to be this long. I guess I could start doing that lorem ipsum "
				+ "garbage, but I'm not sure that anyone would really appreciate that. I think "
				+ "this description is quite long enough, don't you? Oh no! It's starting over!";
		Product longDescription = new Product(getNextPBarcode(), longDesc, "1", "count", 3, 3);
		Product regDescription = new Product(getNextPBarcode(), "3_Hershey's Chocolate 8 oz.",
				"1", "count", 3, 3);
		
		p_f.addProduct(shortDescription);
		p_f.addProductToContainer(shortDescription, SU);
		p_f.addProduct(longDescription);
		p_f.addProductToContainer(longDescription, SU);
		p_f.addProduct(regDescription);
		p_f.addProductToContainer(regDescription, SU);
		
		TimeSource.instance().reset();
		TestBuilder tb = new TestBuilder("Product Report: " + MONTHS + " Months");
		TimeSource.instance().setTime(mar1st2012);
		ProductStatsVisitor visitor = new ProductStatsVisitor(tb, MONTHS);
		ProductManager.instance().accept(visitor);
		tb.print();
		List<List<String>> data = tb.getData();
		
		assertEquals(data.get(0).get(0), "Product Report: " + MONTHS + " Months");
		
		//test 13a
		List<String> sdescTest = data.get(1);
		assertEquals(sdescTest.get(0), "1_13a");
		assertEquals(sdescTest.get(2), "1 count");
		
		//test 13b
		List<String> ldescTest = data.get(2);
		assertEquals(ldescTest.get(0), longDesc);
		assertEquals(ldescTest.get(2), "1 count");
		
		//test 13c
		List<String> rdescTest = data.get(3);
		assertEquals(rdescTest.get(0), "3_Hershey's Chocolate 8 oz.");
		assertEquals(rdescTest.get(2), "1 count");
	}
	
	private List<List<String>> runReport(int months) throws Exception {
    	return runReport(months, now);
    }
    
    private List<List<String>> runReport(int months, Calendar date) throws Exception {
    	TestBuilder testB = new TestBuilder(genTitle(months));
    	TimeSource.instance().setTime(date);
    	ProductStatsVisitor visitor = new ProductStatsVisitor(testB, months);
    	ProductManager.instance().accept(visitor);
    	testB.print();
    	return testB.getData();
    }
    
    private String genTitle(int months) {
    	return "Product Report (" + months + " Months)";
    }
	
	private String getNextPBarcode() {
    	String barcode = "barcode" + PbarcodeCounter;
    	PbarcodeCounter++;
    	return barcode;
    }
	
	private String genRandString(int len) {
    	char[] text = new char[len];
    	for (int i = 0; i < len; i++) {
    		text[i] = ALPHA.charAt(rand.nextInt(ALPHA.length()));
    	}
    	return new String(text);
    }
	
	private int daysBetween(Calendar c1, Calendar c2) {
    	return DateUtils.daysBetween(c1, c2);
    }

}
